package cn.ylyhappy.netdemo

import androidx.collection.SimpleArrayMap
import cn.ylyhappy.netdemo.config.CniaoInterceptor
import cn.ylyhappy.netdemo.config.KtHttpLogInterceptor
import cn.ylyhappy.netdemo.config.LocalCookie
import cn.ylyhappy.netdemo.config.RetryInterceptor
import com.google.gson.Gson
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Cache
import okhttp3.CacheControl
import okhttp3.Call
import okhttp3.Callback
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

class OkHttpApi private constructor() : HttpApi {
    companion object {
        private const val TAG = "OkHttpApi"

        @Volatile
        private var api: OkHttpApi? = null
        fun getInstance(): OkHttpApi {
            return api ?: synchronized(OkHttpApi::class.java) { OkHttpApi().also { api = it } }
        }
    }

    private val mBaseUrl = "http://ylyhappy.cn:48080/"

    private val maxRetry = 0

    private val callMap = SimpleArrayMap<Any, Call>()

//    private val mLoggingInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }

    private val mClient = OkHttpClient.Builder()
        .callTimeout(10, TimeUnit.SECONDS)
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .followRedirects(true)
        .cache(Cache(File("sdcard/cache", "okhttp"), 1024))
//        .cookieJar(CookieJar.NO_COOKIES)
        .cookieJar(LocalCookie())
        .addNetworkInterceptor(CniaoInterceptor())
        .addNetworkInterceptor(KtHttpLogInterceptor().apply {
            mLogTag = "YLY"
            mLogLevel = KtHttpLogInterceptor.LogLevel.BODY
            mColorLevel = KtHttpLogInterceptor.ColorLevel.DEBUG
        })
        .addNetworkInterceptor(RetryInterceptor(maxRetry))
        .build()

    override fun get(params: Map<String, Any>, path: String, callback: IHttpCallback) {
        val url = "$mBaseUrl$path"
//        val url = "http://localhost:48080/doc.html"
        val urlBuilder = url.toHttpUrl().newBuilder()
        params.forEach { entry ->
            urlBuilder.addEncodedQueryParameter(entry.key, entry.value.toString())
        }
        val request = Request.Builder()
            .get()
            .tag(params)
            .url(urlBuilder.build())
            .cacheControl(CacheControl.FORCE_NETWORK)
            .build()
        val newCall = mClient.newCall(request)
        newCall.enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                callback.onFailed(e)
            }

            override fun onResponse(call: Call, response: Response) {
                callback.onSuccess(response)
            }
        })
        callMap.put(request.tag(), newCall)
    }

    // 使用协程形式的get请求，使用runBlocking,也可以使用suspend修饰
    fun get(params: Map<String, Any>, path: String) = runBlocking {
        val url = "$mBaseUrl$path"
//        val url = "http://localhost:48080/doc.html"
        val urlBuilder = url.toHttpUrl().newBuilder()
        params.forEach { entry ->
            urlBuilder.addEncodedQueryParameter(entry.key, entry.value.toString())
        }
        val request = Request.Builder()
            .get()
            .tag(params)
            .url(urlBuilder.build())
            .cacheControl(CacheControl.FORCE_NETWORK)
            .build()
        val newCall = mClient.newCall(request)
        callMap.put(request.tag(), newCall)
        newCall.call()
    }

    /**
     *  自定义扩展函数， 扩展okttp的Call的异步执行方式，结合coroutines，返回DataResult的数据响应
     */
    private suspend fun Call.call(async: Boolean = true): Response {
        return suspendCancellableCoroutine { continuation ->
            if (async) {
                enqueue(object : Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        if (continuation.isCancelled) return
                        continuation.resumeWithException(e)
                    }

                    override fun onResponse(call: Call, response: Response) {
                        continuation.resume(response)
                    }
                })
            } else {
                continuation.resume(execute())
            }
            continuation.invokeOnCancellation {
                try {
                    cancel()
                } catch (ex: Exception) {
                    ex.printStackTrace()
                }
            }
        }
    }

    override fun post(body: Any, path: String, callback: IHttpCallback) {
        val url = "$mBaseUrl$path"
        val request = Request.Builder()
            .post(Gson().toJson(body).toRequestBody())
//            .url(url)
            .url("https://testapi.cniao5.com/accounts/login")
            .tag(body)
            .build()
        val newCall = mClient.newCall(request)
        newCall.enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                callback.onFailed(e)
            }

            override fun onResponse(call: Call, response: Response) {
                callback.onSuccess(response)
            }
        })
        callMap.put(request.tag(), newCall)
    }

    override fun cancelRequest(tag: Any) {
        callMap.get(tag)?.cancel()
    }

    override fun cancelAllRequest() {
        for (i in 0 until callMap.size()) {
            callMap.get(callMap.keyAt(i))?.cancel()
        }
    }
}